package com.basic.util

import java.io.Closeable
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import java.math.BigInteger
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException

/**
 * @author Peter Liu
 * @since 2023/5/6 19:57
 *
 */
object Md5 {
    private val HEX_DIGITS_UPPER =
        charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
    private val HEX_DIGITS_LOWER =
        charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')

    /**
     * 获取 文件的md5
     * 计算方式性能优化
     * 参考：https://juejin.im/post/583e2172128fe1006bf66bc8#heading-9
     */
    fun md5File(file: File?): String {
        val messageDigest: MessageDigest
        var randomAccessFile: RandomAccessFile? = null
        return try {
            messageDigest = MessageDigest.getInstance("MD5")
            randomAccessFile = RandomAccessFile(file, "r")
            val bytes = ByteArray(2 * 1024 * 1024)
            var len = 0
            while (randomAccessFile.read(bytes).also { len = it } != -1) {
                messageDigest.update(bytes, 0, len)
            }
            val bigInt = BigInteger(1, messageDigest.digest())
            val md5: StringBuilder = StringBuilder(bigInt.toString(16))
            while (md5.length < 32) {
                md5.insert(0, "0")
            }
            md5.toString()
        } catch (e: Exception) {
            throw RuntimeException(e)
        } finally {
            closeQuietly(randomAccessFile)
        }
    }

    fun md5(data: ByteArray?): ByteArray? {
        return if (data == null || data.size <= 0) null else try {
            val md = MessageDigest.getInstance("MD5")
            md.update(data)
            md.digest()
        } catch (e: NoSuchAlgorithmException) {
            null
        }
    }

    fun md5(data: String?): ByteArray? {
        return md5(data?.toByteArray())
    }

    fun md5Hex(bytesArray: ByteArray?, isUpperCase: Boolean = true): String {
        val bytes = md5(bytesArray)
        if (bytes == null) return ""
        val hexDigits: CharArray =
            if (isUpperCase) HEX_DIGITS_UPPER else HEX_DIGITS_LOWER
        val len = bytes.size
        if (len <= 0) return ""
        val ret = CharArray(len shl 1)
        var i = 0
        var j = 0
        while (i < len) {
            ret[j++] = hexDigits[bytes[i].toInt() shr 4 and 0x0f]
            ret[j++] = hexDigits[bytes[i].toInt() and 0x0f]
            i++
        }
        return String(ret)
    }

    fun md5Hex(str: String?, isUpperCase: Boolean = true): String {
        return md5Hex(str?.toByteArray(), isUpperCase)
    }

    fun closeQuietly(closeable: Closeable?) {
        try {
            closeable?.close()
        } catch (ioe: IOException) {
            // ignore
        }
    }
}